const domain = require('domain');
const Cookies = require('cookies');
const onHeaders = require('on-headers');
const debug = require('debug')('AV:cookieSession');

module.exports = function(av) {
    return function(opts) {
        opts = opts || {};

        // name - previously "opts.key"
        let name = opts.name || opts.key || 'avos:sess';

        // secrets
        let keys = opts.keys;
        if (!keys && opts.secret) {
            keys = [opts.secret];
        }

        // defaults
        if (!opts.overwrite) {
            opts.overwrite = true;
        }
        opts.httpOnly = true;
        opts.signed = true;

        if (!keys && opts.signed) {
            throw new Error('.keys required for avos cookie sessions.');
        }

        debug('session options %j', opts);

        return async function cookieSession(ctx, next) {
            let req = ctx.req;
            let res = ctx.res;
            
                let cookies = req.sessionCookies = new Cookies(req, res, keys);

                // to pass to Session()
                req.sessionOptions = opts;
                req.sessionKey = name;

                onHeaders(res, function setHeaders() {
                    let session = null;
                    let user = av.User.current();
                    if (user) {
                        session = {
                            _uid: user.id,
                            _sessionToken: user._sessionToken
                        };
                    }
                    if (!session) {
                        debug('clear session');
                        cookies.set(name, '', opts);
                    } else {
                        debug('session %j', session);
                        cookies.set(name, encode(session), opts);
                    }
                });

                let session = {};
                let json = cookies.get(name, opts);
                if (json) {
                    session = decode(json);
                }
                let uid = session._uid;
                let sessionToken = session._sessionToken;
                req.AV = req.AV || {};

                let logInByIdAndSessionToken = new Promise((resolve, reject) => {
                    let doLogInByIdAndSessionToken = function(){
                        av.Cloud.logInByIdAndSessionToken(uid, sessionToken, opts.fetchUser, function(err, user) {
                            if(err) {
                                debug('sessionToken invalid, uid: %s', uid);
                                av.User.logOut();
                                delete req.AV.user;
                                reject('sessionToken invalid, uid: ' + uid)
                            } else {
                                req.AV.user = user;
                                resolve()
                            }
                        });
                    }
                    if (process.domain) {
                        doLogInByIdAndSessionToken();
                    }else{
                        let d = domain.create();
                        d.add(req);
                        d.add(res);
                        d.run(doLogInByIdAndSessionToken);
                    }
                    
                })
                if (uid && sessionToken) {
                    await logInByIdAndSessionToken;
                    await next()
                } else {
                    if (process.domain) {
                        av.User.logOut();
                    }else{
                        let d = domain.create();
                        d.add(req);
                        d.add(res);
                        d.run(av.User.logOut);
                    }
                    
                    delete req.AV.user;
                    await next();
                }
            
        }
    }
}
/**
 * Decode the base64 cookie value to an object.
 *
 * @param {String} string
 * @return {Object}
 * @private
 */

function decode(string) {
    var body = new Buffer(string, 'base64').toString('utf8');
    return JSON.parse(body);
}

/**
 * Encode an object into a base64-encoded JSON string.
 *
 * @param {Object} body
 * @return {String}
 * @private
 */

function encode(body) {
    body = JSON.stringify(body);
    return new Buffer(body).toString('base64');
}